package org.quincy.rock.core.dao.sql;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import org.quincy.rock.core.dao.NameInterceptor;
import org.quincy.rock.core.dao.ValueInterceptor;
import org.quincy.rock.core.exception.IllegalIdentifierException;
import org.quincy.rock.core.exception.UnsupportException;
import org.quincy.rock.core.lang.DataType;
import org.quincy.rock.core.util.ConvertUtil;
import org.quincy.rock.core.util.CoreUtil;
import org.quincy.rock.core.util.StringUtil;

/**
 * <b>条件。</b>
 * <p><b>详细说明：</b></p>
 * <!-- 在此添加详细说明 -->
 * 无。
 * 
 * @version 1.0
 * @author mex2000
 * @since 1.0
 */
public class Condition extends OpExpression {
	/**
	 * serialVersionUID。
	 */
	private static final long serialVersionUID = 6268619176431423664L;

	/**
	 * 属性名或字段名
	 */
	private String name;
	/**
	 * 二元操作符的右值
	 */
	private Object value;
	/**
	 * BETWEEN操作符的右值1
	 */
	private Object value1;
	/**
	 * BETWEEN操作符的右值2
	 */
	private Object value2;
	/**
	 * IN操作符的右值
	 */
	private List<Object> values;
	/**
	 * 右值数据类型
	 */
	private DataType type;
	/**
	 * like匹配模式
	 */
	private Match match;

	public Condition() {
	}

	private Condition(String name, boolean isNull) {
		Objects.requireNonNull(name);
		this.op(isNull ? Op.ISNULL : Op.ISNOTNULL);
	}

	private Condition(Op op, DataType type, String name, Object value) {
		Objects.requireNonNull(op);
		Objects.requireNonNull(name);
		Objects.requireNonNull(value);
		this.op(op);
		if (op == Op.LIKE || op == Op.NOTLIKE)
			this.match(Match.FULL);
		this.type = type == null ? DataType.of(value) : type;
		this.name = name;
		this.value = value;
	}

	private Condition(boolean notlike, Match match, String name, String value) {
		Objects.requireNonNull(match);
		Objects.requireNonNull(name);
		Objects.requireNonNull(value);
		this.op(notlike ? Op.NOTLIKE : Op.LIKE);
		this.match = match;
		this.type = DataType.STRING;
		this.name = name;
		this.value = value;
	}

	private Condition(boolean notBetween, DataType type, String name, Object value1, Object value2) {
		Objects.requireNonNull(type);
		Objects.requireNonNull(name);
		Objects.requireNonNull(value1);
		Objects.requireNonNull(value2);
		this.op(notBetween ? Op.NOTBETWEEN : Op.BETWEEN);
		this.type = type == null ? DataType.of(value1) : type;
		this.name = name;
		this.value1 = value1;
		this.value2 = value2;
	}

	private Condition(boolean notIn, DataType type, String name, Object[] values) {
		Objects.requireNonNull(type);
		Objects.requireNonNull(name);
		Objects.requireNonNull(values);
		this.op(notIn ? Op.NOTIN : Op.IN);
		this.type = type == null ? DataType.of(values[0]) : type;
		this.name = name;
		for (Object v : values) {
			this.addValue(v);
		}
	}

	@Override
	public Op op() {
		Op op = super.op();
		return op == null ? Op.EQUAL : op;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Condition op(Op op) {
		super.op(op);
		return this;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		if (!StringUtil.isValidHierarchyIdentifier(name))
			throw new IllegalIdentifierException(name);
		this.name = name;
	}

	public Object getValue() {
		return value;
	}

	public void setValue(Object value) {
		this.value = value;
	}

	public Object getValue1() {
		return value1;
	}

	public void setValue1(Object value1) {
		this.value1 = value1;
	}

	public Object getValue2() {
		return value2;
	}

	public void setValue2(Object value2) {
		this.value2 = value2;
	}

	public List<Object> getValues() {
		if (values == null)
			values = new ArrayList<>();
		return values;
	}

	public void setValues(List<Object> values) {
		this.values = values;
	}

	public void addValue(Object v) {
		this.getValues().add(v);
	}

	public DataType type() {
		return type == null ? DataType.STRING : type;
	}

	public Condition type(DataType type) {
		this.type = type;
		return this;
	}

	public String getType() {
		return type().name();
	}

	public void setType(String type) {
		this.type = DataType.of(type);
	}

	public Match match() {
		return match == null ? Match.FULL : match;
	}

	public Condition match(Match match) {
		this.match = match;
		return this;
	}

	public String getMatch() {
		return match().name();
	}

	public void setMatch(String match) {
		this.match = Match.valueOf(match.toUpperCase());
	}

	@Override
	public StringBuilder build(StringBuilder buffer, NameInterceptor nameInterceptor,
			ValueInterceptor valueInterceptor) {
		Op op = this.op();
		DataType dt = this.type();
		switch (op) {
		case EQUAL:
		case NOTEQUAL:
		case GREATER:
		case GREATEREQUAL:
		case LESS:
		case LESSEQUAL:
			if (value != null) {
				appendName(buffer, nameInterceptor).append(StringUtil.CHAR_SPACE).append(op.op())
						.append(StringUtil.CHAR_SPACE)
						.append(valueInterceptor.apply(ConvertUtil.convert(value, dt), dt));
			}
			break;
		case LIKE:
		case NOTLIKE:
			if (value != null) {
				appendName(buffer, nameInterceptor).append(StringUtil.CHAR_SPACE).append(op.op())
						.append(StringUtil.CHAR_SPACE).append(valueInterceptor.apply(value, DataType.STRING, match()));
			}
			break;
		case ISNULL:
		case ISNOTNULL:
			appendName(buffer, nameInterceptor).append(StringUtil.CHAR_SPACE).append(op.op());
			break;
		case BETWEEN:
		case NOTBETWEEN:
			if (value1 != null && value2 != null) {
				appendName(buffer, nameInterceptor).append(StringUtil.CHAR_SPACE).append(op.op())
						.append(StringUtil.CHAR_SPACE)
						.append(valueInterceptor.apply(ConvertUtil.convert(value1, dt), dt)).append(" AND ")
						.append(valueInterceptor.apply(ConvertUtil.convert(value2, dt), dt));
			}
			break;
		case IN:
		case NOTIN:
			if (!CoreUtil.isEmpty(values)) {
				appendName(buffer, nameInterceptor).append(StringUtil.CHAR_SPACE).append(op.op()).append(" ( ");
				values.forEach((v) -> buffer.append(valueInterceptor.apply(ConvertUtil.convert(v, dt), dt))
						.append(StringUtil.CHAR_COMMA));
				buffer.setCharAt(buffer.length() - 1, ')');
			}
			break;
		default:
			throw new UnsupportException(buffer.toString());
		}
		return buffer;
	}

	private StringBuilder appendName(StringBuilder buffer, NameInterceptor nameInterceptor) {
		if (nameInterceptor == null)
			buffer.append(name);
		else {
			int pos = name.lastIndexOf(StringUtil.CHAR_DOT);
			if (pos == -1) {
				buffer.append(nameInterceptor.applyColumnName(name));
			} else {
				buffer.append(nameInterceptor.applyColumnName(name.substring(0, pos), name.substring(pos + 1)));
			}
		}
		return buffer;
	}

	public static Condition isNull(String name) {
		return new Condition(name, true);
	}

	public static Condition isNotNull(String name) {
		return new Condition(name, false);
	}

	public static Condition like(Match match, String name, String value) {
		return new Condition(false, match, name, value);
	}

	public static Condition notLike(Match match, String name, String value) {
		return new Condition(true, match, name, value);
	}

	public static Condition between(DataType type, String name, Object value1, Object value2) {
		return new Condition(false, type, name, value1, value2);
	}

	public static Condition notBetween(DataType type, String name, Object value1, Object value2) {
		return new Condition(true, type, name, value1, value2);
	}

	public static Condition in(DataType type, String name, Object... values) {
		return new Condition(false, type, name, values);
	}

	public static Condition notIn(DataType type, String name, Object... values) {
		return new Condition(true, type, name, values);
	}

	public static Condition of(Op op, DataType type, String name, Object value) {
		return new Condition(op, type, name, value);
	}
}
